﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace d39
{
    public static class EffectShapes
    {
        private static List<Vector> directions = new List<Vector> { Vector.N, Vector.NE, Vector.E, Vector.SE, Vector.S, Vector.SW, Vector.W, Vector.NW };
        
        private static List<IEnumerable<Vector>> circlePatterns = new List<IEnumerable<Vector>> {
            new[] {Vector.E, Vector.S, Vector.E},
            new[] {Vector.S, Vector.E, Vector.S},
            new[] {Vector.S, Vector.W, Vector.S},
            new[] {Vector.W, Vector.S, Vector.W},
            new[] {Vector.W, Vector.N, Vector.W},
            new[] {Vector.N, Vector.W, Vector.N},
            new[] {Vector.N, Vector.E, Vector.N},
            new[] {Vector.E, Vector.N, Vector.E},
        };

        public static EffectShape FromChar(char ch)
        {
            return (EffectShape)(ushort)ch;
        }
        public static char ToChar(this EffectShape shape)
        {
            return (char)(ushort)shape;
        }

        public static bool HasRadius(this EffectShape shape)
        {
            return shape == EffectShape.FilledCircle || shape == EffectShape.HollowCircle || shape == EffectShape.Cone || shape == EffectShape.Line;
        }

        public static bool HasDirection(this EffectShape shape)
        {
            return shape == EffectShape.Cone || shape == EffectShape.Line;
        }

        public static bool HasExtents(this EffectShape shape)
        {
            return shape == EffectShape.HollowRectangle || shape == EffectShape.FilledRectangle;
        }

        #region Comments about the points patterns
        // The way this works is as follows. There are eight segments, N-NE, NE-E, E-SE, SE-S etc. To render the corners at a given radius, you
        // take steps from the topmost point in the following order:

        //            N-NE   NE-E E-SE   SE-S S-SW   SW-W W-NW   NW-N
        // Radius 1:  E         S S         W W         N N         E
        // Radius 2:  ES       ES SW       SW WN       WN NE       NE
        // Radius 3:  ESE     SES SWS     WSW WNW     NWN NEN     ENE
        // Radius 4:  ESEE   SSES SWSS   WWSW WNWW   NNWN NENN   EENE
        // Radius 5:  ESEES ESSES SWSSW SWWSW WNWWN WNNWN NENNE NEENE

        // So the patterns are:
        //            ESE   r-SES SWS   r-WSW WNW   r-NWN NEN   r-ENE
        // where "r" indicates reversedness.


        // 1
        // # #  Each seg is one step: E, S, S, W, W, N, N, E
        // # #  For north cone, diagonal is E, N

        // 2
        //   # #
        // # # # #  Each seg is two steps, ES, ES, SW, SW, WN, WN, NE, NE
        // # # # #  For north cone, diagonal is E, N
        //   # #

        // 3
        //     # #
        //   # # # #
        // # # # # # # Each seg is three steps, ESE, SES, SWS, WSW, WNW, NWN, NEN, ENE
        // # # # # # # For north cone, diagonal is E, N, E, N
        //   # # # #
        //     # #

        // 4
        //          # #
        //      # #     # #
        //      #   # #   #
        //    #   #     #   # Each seg is four steps, ESEE, SSES, SWSS, WWSW, WNWW, NNWN, NENN, EENE
        //    #   #     #   # For north cone, diagonal is E, N, E, N, E, N
        //      #   # #   #
        //      # #     # #
        //          # #


        // 5
        //           # #
        //       # # # # # #
        //     # # # # # # # #
        //     # # # # # # # #
        //   # # # # # # # # # #
        //   # # # # # # # # # # For north cone, diagonal is E, N, E, N, E, N
        //     # # # # # # # #
        //     # # # # # # # #
        //       # # # # # #
        //           # #
        #endregion

        public static IEnumerable<Coordinate> HollowCircle(Coordinate startCorner, int radius)
        {
            return FilledCircle(startCorner, radius).Concat(FilledCircle(startCorner, radius - 1).Reverse());
        }

        public static IEnumerable<Coordinate> FilledCircle(Coordinate startCorner, int radius) {

            var pos = startCorner + radius * Vector.N;

            for (int i = 0; i < 8; i++)
            {
                var pattern = Util.Pattern(radius, circlePatterns[i]);
                if (i % 2 != 0) pattern = pattern.Reverse();
                foreach (var dir in pattern)
                {
                    yield return pos;
                    pos += dir;
                }
            }
            yield return pos;
        }

        public static IEnumerable<Coordinate> Cone(Coordinate startCorner, Vector direction, int radius) {
            var dirpos = directions.IndexOf(direction);

            if (dirpos % 2 != 0)
            {
                yield return startCorner;
                var pos = startCorner + radius * directions[(dirpos + 7) % 8];
                for (int i = dirpos - 1; i < dirpos + 1; i++)
                {
                    var pattern = Util.Pattern(radius, circlePatterns[i]);
                    if (i % 2 != 0) pattern = pattern.Reverse();
                    foreach (var dir in pattern)
                    {
                        yield return pos;
                        pos += dir;
                    }
                }
                yield return pos;
            }
            else
            {
                var pos = startCorner + radius * direction;
                foreach (var dir in Util.Pattern(radius, circlePatterns[dirpos])) // Definitely not reversed because dirpos is by definition even in this case
                {
                    yield return pos;
                    pos += dir;
                }

                var dist = pos - startCorner;
                var distCount = Math.Abs(dist.X) + Math.Abs(dist.Y);
                var perpendicular = directions[(dirpos + 6) % 8];

                foreach (var dir in Util.Pattern(distCount, -direction, perpendicular))
                {
                    yield return pos;
                    pos += dir;
                }
                foreach (var dir in Util.Pattern(distCount, perpendicular, direction))
                {
                    yield return pos;
                    pos += dir;
                }

                foreach (var dir in Util.Pattern(radius, circlePatterns[(dirpos + 7) % 8]).Reverse()) // Definitely reversed because dirpos-1 is by definition odd
                {
                    yield return pos;
                    pos += dir;
                }
            }
        }

        public static IEnumerable<Coordinate> Line(Coordinate startCorner, Vector direction, int radius)
        {
            if (direction.X == 0)
            {
                yield return startCorner;
                yield return startCorner + Vector.E;
                yield return startCorner + Vector.E + radius * direction.Y * Vector.S;
                yield return startCorner + radius * direction.Y * Vector.S;
            }
            else if (direction.Y == 0)
            {
                yield return startCorner;
                yield return startCorner + Vector.S;
                yield return startCorner + Vector.S + radius * direction.X * Vector.E;
                yield return startCorner + radius * direction.X * Vector.E;
            }
            else
            {
                var pos = startCorner;
                var h = new Vector(direction.X, 0);
                var v = new Vector(0, direction.Y);
                foreach (var dir in Util.Pattern(radius * 2, h, v).Concat(Util.Pattern(radius * 2, -h, -v)))
                {
                    yield return pos;
                    pos += dir;
                }
            }
        }

        public static IEnumerable<Coordinate> HollowRectangle(Coordinate startCorner, int north, int east, int south, int west)
        {
            return FilledRectangle(startCorner, north, east, south, west).Concat(FilledRectangle(startCorner, north - 1, east - 1, south - 1, west - 1).Reverse());
        }

        public static IEnumerable<Coordinate> FilledRectangle(Coordinate startCorner, int north, int east, int south, int west)
        {
            yield return startCorner + east * Vector.E + north * Vector.N;
            yield return startCorner + east * Vector.E + south * Vector.S;
            yield return startCorner + west * Vector.W + south * Vector.S;
            yield return startCorner + west * Vector.W + north * Vector.N;
            yield return startCorner + east * Vector.E + north * Vector.N;
        }
    }
}